/*
 * Copyright (C) 2008 Peter Kling
 * 
 * This file is part of 'A Kind of Billiard', a graphical billiard simulation.
 * 
 * 'A Kind of Billiard' is free software: you can redistribute it and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * 'A Kind of Billiard' is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with 'A Kind of Billiard'. If not, see
 * <http://www.gnu.org/licenses/>.
 */

package de.akob.mathematics;

/**
 * Rectangular (<code>double</code>) matrices.
 */
public class MathMatrix implements Cloneable {
	// storage for matrix data (column-major order)
	private double[] data;
	
	// number of rows/columns
	public final int n, m;
	
	/**
	 * Constructs an (<code>n</code> x <code>m</code>) zero matrix.
	 * 
	 * @param n number of matrix rows
	 * @param m number of matrix columns
	 * @throws IllegalArgumentException if <code>n</code> or <code>m</code> are non-positive
	 */
	public MathMatrix(int n, int m) {
		if (n <= 0 || m <= 0)
			throw new IllegalArgumentException("dimensional parameters have to be positive");
		this.n = n;
		this.m = m;
		data = new double[n * m];
	}
	
	/**
	 * Constructs an (<code>n</code> x <code>m</code>) matrix and initializes it with the given elements. The
	 * first m elements will make up the first row, the second m elements the second row and so on (row-major order).
	 * 
	 * @param n number of matrix rows
	 * @param m number of matrix columns
	 * @param data varargs/array of matrix elements in row-major order
	 * @throws IllegalArgumentException if the number of given elements does not match <code>n*m</code> or if
	 *             <code>n</code> or <code>m</code> are non-positive
	 */
	public MathMatrix(int n, int m, double... data) {
		if (data.length != n * m)
			throw new IllegalArgumentException("number of data elements does not match n*m");
		if (n <= 0 || m <= 0)
			throw new IllegalArgumentException("dimensional parameters have to be positive");
		this.n = n;
		this.m = m;
		this.data = data;
		transpose();
	}
	
	/**
	 * Constructs an (<code>n</code> x <code>m</code>) matrix and initializes it with the given elements. The
	 * first m elements will make up the first row, the second m elements the second row and so on (row-major order).
	 * Note that this is a convenient constructor that will convert all float values into double values and use these
	 * internally.
	 * 
	 * @param n number of matrix rows
	 * @param m number of matrix columns
	 * @param data varargs/array of matrix elements in row-major order
	 * @throws IllegalArgumentException if the number of given elements does not match <code>n*m</code> or if
	 *             <code>n</code> or <code>m</code> are non-positive
	 */
	public MathMatrix(int n, int m, float... data) {
		if (data.length != n * m)
			throw new IllegalArgumentException("number of data elements does not match n*m");
		if (n <= 0 || m <= 0)
			throw new IllegalArgumentException("dimensional parameters have to be positive");
		this.n = n;
		this.m = m;
		this.data = new double[n * m];
		for (int i = 0; i < this.data.length; i++)
			this.data[i] = data[i];
		transpose();
	}
	
	/**
	 * Adds the given (<code>n</code> x <code>m</code>) matrix.
	 * 
	 * @param matrix matrix to add
	 * @throws IllegalArgumentException if matrix dimensions do not match.
	 */
	public void add(MathMatrix matrix) {
		if (matrix.n != n || matrix.m != m)
			throw new IllegalArgumentException("matrix dimensions do not match");
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				set(i, j, get(i, j) + matrix.get(i, j));
	}
	
	@Override
	public MathMatrix clone() {
		MathMatrix matrix = new MathMatrix(n, m, getElements_rowMajor().clone());
		return matrix;
	}
	
	/**
	 * Returns the matrix element in row i and column j.
	 * 
	 * @param i row of element to return
	 * @param j column of element to return
	 * @return element at position (<code>i</code>,<code>j</code>) in matrix
	 */
	public double get(int i, int j) {
		return data[(j - 1) * n + (i - 1)];
	}
	
	/**
	 * Returns an array containing the matrix elements in column-major order.
	 * 
	 * @return array of matrix elements in column-major order
	 */
	public double[] getElements_colMajor() {
		return data;
	}
	
	/**
	 * Returns an array containing the matrix elements in row-major order. Note that this method - in contrast to
	 * <code>getElements_colMajor</code> - will not return a reference to the internal elements, but rather a copy of
	 * the data.
	 * 
	 * @return array of matrix elements in row-major order
	 */
	public double[] getElements_rowMajor() {
		double[] data_rowMajor = new double[n * m];
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				data_rowMajor[(i - 1) * m + (j - 1)] = get(i, j);
		return data_rowMajor;
	}
	
	/**
	 * Returns the transpose of this matrix.
	 * 
	 * @return transpose of this matrix
	 */
	public MathMatrix getTranspose() {
		MathMatrix matrix = new MathMatrix(m, n);
		for (int i = 1; i <= m; i++)
			for (int j = 1; j <= n; j++)
				matrix.set(i, j, get(j, i));
		return matrix;
	}
	
	/**
	 * Sets the matrix element in row i and column j.
	 * 
	 * @param i row of element to set
	 * @param j column of element to set
	 * @param value value element will be set to
	 */
	public void set(int i, int j, double value) {
		data[(j - 1) * n + (i - 1)] = value;
	}
	
	/**
	 * Subtracts the given (<code>n</code> x <code>m</code>) matrix.
	 * 
	 * @param matrix matrix to subtract
	 * @throws IllegalArgumentException if matrix dimensions do not match.
	 */
	public void sub(MathMatrix matrix) {
		if (matrix.n != n || matrix.m != m)
			throw new IllegalArgumentException("matrix dimensions do not match");
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				set(i, j, get(i, j) - matrix.get(i, j));
	}
	
	/**
	 * Returns a readable representation of this matrix.
	 * 
	 * @return readable representation of this matrix
	 */
	@Override
	public String toString() {
		String str = "";
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m - 1; j++)
				str += get(i, j) + "\t";
			str += get(i, m) + "\n";
		}
		return str;
	}
	
	/**
	 * Transposes this matrix.
	 */
	public void transpose() {
		data = getElements_rowMajor();
	}
}
